package org.epic.regexp.views;

import gnu.regexp.RE;
import gnu.regexp.REException;
import gnu.regexp.REMatch;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.ViewPart;

public class RegExpView extends ViewPart {

    class focusListener implements FocusListener {
        public void focusGained(FocusEvent e) {
            activeInput = e.getSource();
        }

        public void focusLost(FocusEvent e) {
        }
    };

    class DebugInfo {

        private List<SubexpressionPos> subexpressions = new ArrayList<SubexpressionPos>();

        private List<SubexpressionPos> bracketsInRegexp = new ArrayList<SubexpressionPos>();

        private List<REMatch> allMatches = new ArrayList<REMatch>();

        private String input;

        private String regexp;

        private String matchString;

        private boolean matchesInitialized = false;

        private int matchingBracketsCount = 0;

        int eflags = 0;

        public DebugInfo() {
        }

        public void setInput(String input) {
            this.input = input;
        }

        public void setRegexp(String regexp) {
            this.regexp = regexp;
        }

        public void setMatchString(String match) {
            this.matchString = match;
        }

        public String getInput() {
            return input;
        }

        public String getRegExp() {
            return regexp;
        }

        public String getMatchString() {
            return matchString;
        }

        public void addBracketPosition(int start, int end) {
            bracketsInRegexp.add(new SubexpressionPos(start, end));
        }

        public void addSubexpressionPosition(int start, int end) {

            subexpressions.add(new SubexpressionPos(start, end));
        }

        public SubexpressionPos getSubexpressionPosition(int index) {
            return (SubexpressionPos) subexpressions.get(index);
        }

        public int getSubexpressionCount() {
            return subexpressions.size();
        }

        public REMatch[] getMatches(int index) {

            int checkEflags = 0;

            if (ignoreCaseCheckBox.getSelection()) {
                checkEflags |= RE.REG_ICASE;
            }

            if (multilineCheckBox.getSelection()) {
                checkEflags |= RE.REG_MULTILINE;
            }

            if (!matchesInitialized || eflags != checkEflags) {
                eflags = checkEflags;
                initMatches();
            }

            return (REMatch[]) allMatches.toArray(new REMatch[0]);
        }

        public int geMatchingBracketsCount() {
            return matchingBracketsCount;
        }

        public void initMatches() {
            // Get longest match
            String reg = null;
            boolean found = false;
            RE re = null;

            this.allMatches.clear();

            try {
                for (int i = bracketsInRegexp.size() - 1; i >= 0; i--) {
                    SubexpressionPos pos = (SubexpressionPos) bracketsInRegexp
                            .get(i);
                    reg = regexp.substring(0, pos.getEnd() + 1);

                    re = new RE(reg, eflags);
                    if (re.getAllMatches(matchString).length > 0) {
                        matchingBracketsCount = i + 1;
                        found = true;

                        break;
                    }
                }

                if (re != null && found) {
                    REMatch[] matches = re.getAllMatches(matchString);
                    for (int j = 0; j < matches.length; j++) {
                        this.allMatches.add(matches[j]);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            this.matchesInitialized = true;

        }

    };

    class SubexpressionPos {
        int start;

        int end;

        public SubexpressionPos(int start, int end) {
            this.start = start;
            this.end = end;
        }

        private int getStart() {
            return start;
        }

        private int getEnd() {
            return end;
        }
    };

    private Composite panel;

    private List<Color> colorTable = new ArrayList<Color>();

    private Action validateAction, cutAction, copyAction, pasteAction;

    private Action stopDebugAction, forwardDebugAction, backDebugAction;

    private StyledText regExpText;

    private StyledText matchText;

    private Label resultImageLabel;

    private Button ignoreCaseCheckBox, multilineCheckBox;

    private Object activeInput = null;

    private DebugInfo debugInfo = null;

    private int debugPosition = 0;

    private static final String SHORTCUTS_FILE = "shortcuts";

    /**
     * The constructor.
     */
    public RegExpView() {
    }

    /**
     * This is a callback that will allow us to create the viewer and initialize
     * it.
     */
    public void createPartControl(Composite parent) {

        makeActions();
        contributeToActionBars();

        panel = new Composite(parent, SWT.NULL);
        //Create a data that takes up the extra space in the dialog .

        buildColorTable();

        GridLayout layout = new GridLayout();
        layout.numColumns = 4;
        panel.setLayout(layout);

        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        data.grabExcessHorizontalSpace = true;
        panel.setLayoutData(data);

        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalSpan = 1;
        Label regExpLabel = new Label(panel, SWT.NONE);
        regExpLabel.setText("RegExp:");
        regExpLabel.setLayoutData(data);

        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalSpan = 1;
        resultImageLabel = new Label(panel, SWT.NONE);
        setResultLabelImage(RegExpImages.RESULT_GRAY);
        resultImageLabel.setLayoutData(data);

        // Insert CheckBoxes
        data = new GridData();
        data.horizontalSpan = 1;
        ignoreCaseCheckBox = new Button(panel, SWT.CHECK);
        ignoreCaseCheckBox.setText("ignore case");
        ignoreCaseCheckBox.setLayoutData(data);

        data = new GridData();
        data.horizontalSpan = 1;
        multilineCheckBox = new Button(panel, SWT.CHECK);
        multilineCheckBox.setText("multiline");
        multilineCheckBox.setLayoutData(data);

        //RegExp
        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalSpan = 4;
        regExpText = new StyledText(panel, SWT.BORDER);
        regExpText.setLayoutData(data);
        regExpText.addFocusListener(new focusListener());

        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalSpan = 4;
        Label matchTextLabel = new Label(panel, SWT.NONE);
        matchTextLabel.setText("Match text:");
        matchTextLabel.setLayoutData(data);

        //Match text
        data = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
        data.horizontalSpan = 4;
        matchText = new StyledText(panel, SWT.BORDER | SWT.H_SCROLL
                | SWT.V_SCROLL);
        matchText.setLayoutData(data);
        matchText.addFocusListener(new focusListener());

        //set the actions for the global action handler
        IActionBars bars = getViewSite().getActionBars();
        bars.setGlobalActionHandler(ActionFactory.CUT.getId(), cutAction);
        bars.setGlobalActionHandler(ActionFactory.COPY.getId(), copyAction);
        bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), pasteAction);

        hookContextMenu();

    }

    private void hookContextMenu() {
        MenuManager menuMgr = new MenuManager("#PopupMenu");
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                fillContextMenu(manager);
            }
        });

        Menu menuRegExp = menuMgr.createContextMenu(regExpText);
        regExpText.setMenu(menuRegExp);

        Menu menuMatch = menuMgr.createContextMenu(matchText);
        matchText.setMenu(menuMatch);
        //getSite().registerContextMenu(menuMgr, );
    }

    private void fillContextMenu(IMenuManager manager) {
        manager.add(cutAction);
        manager.add(copyAction);
        manager.add(pasteAction);
        if(regExpText.isFocusControl()) {
            createShortcutsMenu(manager);
        }

        // Other plug-ins can contribute there actions here
        manager.add(new Separator("Additions"));
    }

    private void buildColorTable() {
        Display display = panel.getDisplay();
        colorTable.add(display.getSystemColor(SWT.COLOR_BLUE));
        colorTable.add(display.getSystemColor(SWT.COLOR_BLACK));
        colorTable.add(display.getSystemColor(SWT.COLOR_RED));
        colorTable.add(display.getSystemColor(SWT.COLOR_DARK_GRAY));
        colorTable.add(display.getSystemColor(SWT.COLOR_DARK_GREEN));
        colorTable.add(new Color(display, 255, 127, 0));
        colorTable.add(display.getSystemColor(SWT.COLOR_DARK_MAGENTA));
        colorTable.add(new Color(display, 201, 141, 141));
        colorTable.add(new Color(display, 214, 179, 74));
        colorTable.add(new Color(display, 204, 74, 214));
    }

    private void createShortcutsMenu(IMenuManager mgr) {
        IMenuManager submenu = new MenuManager("Shortcuts");
        mgr.add(submenu);

        Action shortcut;

        try {
             InputStream is = getClass().getResourceAsStream(SHORTCUTS_FILE);
            
            BufferedReader br = new BufferedReader(new InputStreamReader(is));

            String line;

            while ((line = br.readLine()) != null) {
                StringTokenizer st = new StringTokenizer(line, "\t");

                if (st.countTokens() == 2) {

                    final String shortc = st.nextToken();
                    String descr = st.nextToken();

                    shortcut = new Action() {
                        public void run() {
                            insertShortcut(shortc);
                        }
                    };

                    shortcut.setText(shortc + " - " + descr);
                    submenu.add(shortcut);
                    //}

                    mgr.update(true);
                } else if (st.countTokens() == 1) {
                    String token = st.nextToken();
                    if (token.equals("<DEL>")) {
                        submenu.add(new Separator());
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void contributeToActionBars() {
        IActionBars bars = getViewSite().getActionBars();
        fillLocalToolBar(bars.getToolBarManager());
    }

    private void fillLocalToolBar(IToolBarManager manager) {
        manager.add(stopDebugAction);
        manager.add(backDebugAction);
        manager.add(forwardDebugAction);
        manager.add(new Separator());
        manager.add(validateAction);
    }

    private void makeActions() {

        // Reset debug action
        stopDebugAction = new Action() {
            public void run() {
                resetDebug();
            }
        };
        stopDebugAction.setText("Reset");
        stopDebugAction.setToolTipText("Reset");
        stopDebugAction.setImageDescriptor(RegExpImages.ICON_DEBUG_STOP);

        //	Back debug action
        backDebugAction = new Action() {
            public void run() {
                backDebug();
            }
        };
        backDebugAction.setText("Backward");
        backDebugAction.setToolTipText("Backward");
        backDebugAction.setImageDescriptor(RegExpImages.ICON_DEBUG_BACK);

        //	Forward debug action
        forwardDebugAction = new Action() {
            public void run() {
                forwardDebug();
            }
        };
        forwardDebugAction.setText("Forward");
        forwardDebugAction.setToolTipText("Forward");
        forwardDebugAction.setImageDescriptor(RegExpImages.ICON_DEBUG_FORWARD);

        // Validation action
        validateAction = new Action() {
            public void run() {
                validateRegExp();
            }
        };
        validateAction.setText("Validate RegExp");
        validateAction.setToolTipText("Validate RegExp");
        validateAction.setImageDescriptor(RegExpImages.ICON_RUN);

        // Cut action
        cutAction = new Action("Cut", RegExpImages.EDIT_CUT) {
            public void run() {
                ((StyledText) activeInput).cut();
            }
        };

        // Copy action
        copyAction = new Action("Copy", RegExpImages.EDIT_COPY) {
            public void run() {
                ((StyledText) activeInput).copy();
            }
        };

        // Paste action
        pasteAction = new Action("Paste", RegExpImages.EDIT_PASTE) {
            public void run() {
                ((StyledText) activeInput).paste();
            }
        };

    }

    private void insertShortcut(String text) {
        int selCount = regExpText.getSelectionCount();
        int pos = regExpText.getCaretOffset();
        regExpText.insert(text);

        // Adjust carret offset if no selection was available,
        // so carret is placed after the inserted text
        if (selCount == 0) {
            regExpText.setCaretOffset(pos + text.length());
        }
    }

    public void validateRegExp() {
        boolean result = false;
        int eflags = 0;

        // Reset style
        regExpText.setStyleRange(null);

        // Reset debug poition counter
        debugPosition = 0;

        if (ignoreCaseCheckBox.getSelection()) {
            eflags |= RE.REG_ICASE;
        }

        if (multilineCheckBox.getSelection()) {
            eflags |= RE.REG_MULTILINE;
        }

        try {
            RE re = new RE(regExpText.getText(), eflags);

            REMatch[] matches = re.getAllMatches(matchText.getText());

            result = matches.length > 0 ? true : false;

            // Reset style
            matchText.setStyleRange(null);

            for (int i = 0; i < matches.length; i++) {
                int color = 0;
                for (int j = 1; j <= re.getNumSubs(); j++) {

                    StyleRange styleRange = new StyleRange();

                    styleRange.start = matches[i].getStartIndex(j);
                    styleRange.length = matches[i].getEndIndex(j)
                            - matches[i].getStartIndex(j);

                    Display display = panel.getDisplay();
                    styleRange.foreground = display
                            .getSystemColor(SWT.COLOR_WHITE);
                    //styleRange.fontStyle = SWT.BOLD;

                    styleRange.background = (Color) colorTable.get(color);

                    matchText.setStyleRange(styleRange);
                    // Update text position
                    matchText.setTopIndex(styleRange.start);
                    matchText.setCaretOffset(styleRange.start);
                    int offsetFromLine = styleRange.start
                            - matchText.getOffsetAtLine(matchText
                                    .getLineAtOffset(styleRange.start));
                    matchText.setHorizontalIndex(offsetFromLine);
                    matchText.redraw();

                    if (++color > colorTable.size()) {
                        color = 0;
                    }
                }

            }

        } catch (REException e) {
            e.printStackTrace();
        }

        if (result) {
            setResultLabelImage(RegExpImages.RESULT_GREEN);
        } else {
            setResultLabelImage(RegExpImages.RESULT_RED);
        }
    }

    public void setResultLabelImage(ImageDescriptor descr) {
        Image labelImage = new Image(resultImageLabel.getDisplay(), descr
                .getImageData());
        labelImage.setBackground(panel.getBackground());
        resultImageLabel.setImage(labelImage);
    }

    /**
     * Passing the focus request to the viewer's control.
     */
    public void setFocus() {
        //regExpText.setFocus();
    }

    private void buildDebugRegExp(String input, String match) {
        String convInput;
        String result = "";
        RE re = null;
        boolean inBracket = false;
        boolean escape = false;
        String character;
        int start = 0, end = 0;
        int bracketStart = 0;

        debugInfo = new DebugInfo();
        debugInfo.setInput(input);
        debugInfo.setMatchString(match);

        try {
            // Replace escaped characters
            re = new RE("\\[^.]");
            convInput = re.substitute(input, "..");

            if (convInput.indexOf('(') == -1) {
                for (int i = 0; i < input.length(); i++) {
                    character = input.substring(i, i + 1);

                    if (!inBracket) {
                        re = new RE("[\\[\\{]");
                        if (re.isMatch(character)) {
                            bracketStart = result.length();
                            result += "(" + character;
                            inBracket = true;
                            start = i;
                        } else {
                            if (!escape) {
                                bracketStart = result.length();
                                result += "(";
                                start = i;
                            } else {
                                escape = false;
                            }

                            result += character;
                            if (character.equals("\\")) {
                                escape = true;
                                continue;
                            }

                            if ((i + 1) < input.length()) {
                                String nextChar = input.substring(i + 1, i + 2);

                                re = new RE("[\\*\\+]");
                                if (re.isMatch(nextChar)) {
                                    result += nextChar;
                                    i++;
                                }
                            }

                            if ((i + 1) < input.length()) {
                                String nextChar = input.substring(i + 1, i + 2);

                                re = new RE("[\\?]");
                                if (re.isMatch(nextChar)) {
                                    result += nextChar;
                                    i++;
                                }
                            }

                            result += ")";
                            debugInfo.addSubexpressionPosition(start, i + 1);
                            debugInfo.addBracketPosition(bracketStart, result
                                    .length() - 1);

                        }

                    } else {

                        re = new RE("[\\]\\}]");
                        if (re.isMatch(character)) {
                            if ((i + 1) < input.length()) {

                                String nextChar = input.substring(i + 1, i + 2);

                                if (nextChar.equals("{")) {
                                    result += character + nextChar;
                                    i++;
                                    continue;
                                }

                                re = new RE("[\\+\\*]");
                                if (re.isMatch(nextChar)) {
                                    result += character + nextChar;
                                    i++;

                                    if ((i + 1) < input.length()) {
                                        nextChar = input
                                                .substring(i + 1, i + 2);

                                        re = new RE("[\\?]");
                                        if (re.isMatch(nextChar)) {
                                            result += nextChar;
                                            i++;
                                        }
                                    }
                                    result += ")";
                                    debugInfo.addSubexpressionPosition(start,
                                            i + 1);
                                    debugInfo.addBracketPosition(bracketStart,
                                            result.length() - 1);

                                    inBracket = false;

                                } else {
                                    result += character + ")";
                                    debugInfo.addSubexpressionPosition(start,
                                            i + 1);
                                    debugInfo.addBracketPosition(bracketStart,
                                            result.length() - 1);
                                    inBracket = false;
                                }
                            }
                            // If it's the last character
                            else {
                                result += character + ")";
                                debugInfo
                                        .addSubexpressionPosition(start, i + 1);
                                debugInfo.addBracketPosition(bracketStart,
                                        result.length() - 1);
                                inBracket = false;
                            }

                        } else {
                            result += character;
                        }
                    }

                }

            } else {
                // Handle pre-formatted regexp
                //..................................
                result = input;

                re = new RE("\\((.*?)\\)");

                REMatch[] matches = re.getAllMatches(convInput);

                for (int i = 0; i < matches.length; i++) {
                    for (int j = 1; j <= re.getNumSubs(); j++) {
                        start = matches[i].getStartIndex(j);
                        end = matches[i].getEndIndex(j);
                        debugInfo.addSubexpressionPosition(start, end);
                        debugInfo.addBracketPosition(start, end);
                    }
                }
            }

        } catch (Exception e) {
            result = null;
            e.printStackTrace();
        }

        debugInfo.setRegexp(result);
    }

    private void resetDebug() {
        debugPosition = 0;
        regExpText.setStyleRange(null);
        matchText.setStyleRange(null);
        setResultLabelImage(RegExpImages.RESULT_GRAY);
    }

    private void backDebug() {
        showDebugResult(debugPosition - 1);
    }

    private void forwardDebug() {
        showDebugResult(debugPosition + 1);
    }

    private void showDebugResult(int position) {

        if (debugInfo == null) {
            buildDebugRegExp(regExpText.getText(), matchText.getText());
            position = 1;
        } else if (!debugInfo.getInput().equals(regExpText.getText())
                || !debugInfo.getMatchString().equals(matchText.getText())) {
            buildDebugRegExp(regExpText.getText(), matchText.getText());
            position = 1;
        }

        // Nothing to do
        if (position > debugInfo.getSubexpressionCount() || position < 1) {
            return;
        }

        REMatch[] matches = debugInfo.getMatches(position - 1);

        debugPosition = position;

        StyleRange styleRangeRegExp = new StyleRange();

        Display display = panel.getDisplay();
        styleRangeRegExp.background = display.getSystemColor(SWT.COLOR_BLUE);
        styleRangeRegExp.foreground = display.getSystemColor(SWT.COLOR_WHITE);

        // Colour the regexp
        SubexpressionPos pos = debugInfo.getSubexpressionPosition(position - 1);
        //Reset style
        regExpText.setStyleRange(null);

        styleRangeRegExp.start = pos.getStart();
        styleRangeRegExp.length = pos.getEnd() - pos.getStart();

        regExpText.setStyleRange(styleRangeRegExp);

        //	Update text position
        regExpText.setTopIndex(styleRangeRegExp.start);
        regExpText.setCaretOffset(styleRangeRegExp.start);
        int offsetFromLine = styleRangeRegExp.start
                - regExpText.getOffsetAtLine(regExpText
                        .getLineAtOffset(styleRangeRegExp.start));
        regExpText.setHorizontalIndex(offsetFromLine);
        regExpText.redraw();

        // Colour the matching text
        matchText.setStyleRange(null);

        if (position <= debugInfo.geMatchingBracketsCount()) {
            setResultLabelImage(RegExpImages.RESULT_GREEN);
            for (int i = 0; i < matches.length; i++) {

                StyleRange styleRangeMatch = new StyleRange();
                styleRangeMatch.background = display
                        .getSystemColor(SWT.COLOR_BLUE);
                styleRangeMatch.foreground = display
                        .getSystemColor(SWT.COLOR_WHITE);

                styleRangeMatch.start = matches[i].getStartIndex(position);
                styleRangeMatch.length = matches[i].getEndIndex(position)
                        - matches[i].getStartIndex(position);
                matchText.setStyleRange(styleRangeMatch);
                //	Update text position
                matchText.setTopIndex(styleRangeMatch.start);
                matchText.setCaretOffset(styleRangeMatch.start);
                offsetFromLine = styleRangeMatch.start
                        - matchText.getOffsetAtLine(matchText
                                .getLineAtOffset(styleRangeMatch.start));
                matchText.setHorizontalIndex(offsetFromLine);
                matchText.redraw();
            }

        } else {
            setResultLabelImage(RegExpImages.RESULT_RED);
        }

    }

    /**
     * @param regExpText
     *            The regExpText to set.
     */
    public void setRegExpText(String regexp) {
        regExpText.setText(regexp);
    }

    /**
     * @param matchText
     *            The matchText to set.
     */
    public void setMatchText(String text) {
        matchText.setText(text);
    }

    /**
     * @param state
     *            The state of the ingore checkbox, <code>true</code> or
     *            <code>false</code>
     */
    public void setIgnoreCaseCheckbox(boolean state) {
        ignoreCaseCheckBox.setSelection(state);
    }

    /**
     * @param state
     *            The state of the multiline checkbox, <code>true</code> or
     *            <code>false</code>
     */
    public void setMultilineCheckbox(boolean state) {
        multilineCheckBox.setSelection(state);
    }
}